#  This software code is made available "AS IS" without warranties of any
#  kind.  You may copy, display, modify and redistribute the software
#  code either by itself or as incorporated into your code; provided that
#  you do not remove any proprietary notices.  Your use of this software
#  code is at your own risk and you waive any claim against the author
#  or its affiliates with respect to your use of  this software code. 

# Author: Martin Conte Mac Donell <reflejo@gmail.com>

import re
import urllib2, urllib
import simplejson
from htmlentitydefs import name2codepoint as n2cp

def decode_htmlentities(string):
    def substitute_entity(match):
        ent = match.group(2)
        if match.group(1) == "#":
            return unichr(int(ent))
        else:
            cp = n2cp.get(ent)
            if cp:
                return unichr(cp)
            else:
                return match.group()

    if not isinstance(string, (str, unicode)):
        return string

    entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});")
    val = entity_re.subn(substitute_entity, string)[0]
    return val.replace("\\n", "\n")


ENTITIES_LIMIT = 100

class InvalidFormat(ValueError): pass
class AuthenticateError(Exception): pass
class ConnectionError(Exception): pass

# Decorator for authenticated methods
def authenticated(func):
    def newf(*args, **kw):
        instance = args[0]
        if not instance.is_authenticated():
            raise AuthenticateError('You must call authenticate first')

        return func(*args, **kw)
    return newf


class Element(object): 

    def __init__(self, args):
        for key, value in args.iteritems():
            value = decode_htmlentities(value)
            setattr(self, key, value)

    def __getitem__(self, key):
        return getattr(self, key)


class User(Element):

    def get_full_name(self):
        if not self.name and not self.lastname:
            return None

        return ("%s %s" % (self.name, self.lastname)).strip()

    def __repr__(self):
        return "User <%s>" % repr(self.get_full_name() or self.nickname)


class Need(Element):

    def __init__(self, need, user, app_key=None, helps=[]):
        self.user = User(user)
        self.helps = [Help(h.get('Help', {})) for h in helps]
        self._app_key = app_key
        super(Need, self).__init__(need)

    def __repr__(self):
        return "Need <%s> - %s" % (repr(self.subject), self.user)

    def add_help(self, text):
        return Helps(self._app_key).add(self.id, text)


class Help(Element):

    def __init__(self, help):
        self.user = User(help.pop('User'))
        super(Help, self).__init__(help)

    def __repr__(self):
        return "Help <%s> - %s" % (repr(self.text), self.user)


class Needish(object):
    """
    Full implementation of Needish API. 
    You can also pass limit, order and since to almost every method.

    >>> from pyneedish import Users, Helps, Needs
    >>> u = Users('d29c773fa210xxxxxxxxxxxxxxxxxxxx')
    >>> user = u.authenticate('Reflejo@gmail.com', 'xxxxx')
    >>> user
    User <u'Mart\xedn Conte Mac Donell'>
    >>> user.nickname
    u'Reflejo'
    >>> friends = u.friends(user.id)
    >>> u.profile(user.id)
    User <u'Mart\xedn Conte Mac Donell'>

    >>> n = Needs('d29c773fa210xxxxxxxxxxxxxxxxxxxx')
    >>> n.authenticate('Reflejo@gmail.com', 'xxxxx')
    >>> need = n.add('Testing api', 'Testing api in sandbox')
    >>> needs = n.all()
    >>> need = needs.next()
    >>> need.subject
    u'La respuesta a la Vida, el Universo y a Todo ?'
    >>> need.text
    u'Cual es la respuesta a la Vida, el Universo y a Todo ?'
    >>> need.user
    User <u'Nelson Fernandez'>
    >>> need.user.nickname
    u'kascote'

    >>> needs = n.friends()
    >>> needs = n.follow()
    >>> helps = n.helps('12303')
    >>> needs = n.helped()
    >>> needs = n.hot()
    >>> needs.next()
    Need <u'ESTUDIO DE SONDAJE'>, User <u'mar\xeda contreras'>
    >>> needs = n.terms('hola')
    >>> needs.next()
    Need <u'Aumentar portafolio artistico'>, User <u'Marjorie Valencia'>
    >>> needs = n.tag('php')
    >>> needs.next()
    Need <u'Apoyar la cadidatura de @Prisca81 a Miss Tuittera'>, \
    User <u'Leonor Espinoza'>
    >>> needs = n.tracker()
    >>> needs = n.user('4143')
    >>> needs.next()
    Need <u'AYUDANTIAS'>, User <u'Alejandra Olgui\xadn'>
    >>> n.view('12303')
    Need <u'La respuesta a la Vida, el Universo y a Todo ?'>, \
    User <u'Nelson Fernandez'>

    >>> h = Helps('d29c773fa210xxxxxxxxxxxxxxxxxxxx')
    >>> h.authenticate('Reflejo@gmail.com', 'xxxxx')
    >>> help = h.add('13251', 'testing')
    """
    _auth_header = ()
    api_url = 'http://api.needish.com/'

    def __init__(self, app_key):
        self._app_key = app_key

    def is_authenticated(self):
        return bool(Needish._auth_header)

    def _build_url(self, path, get):
        get = urllib.urlencode(get)
        base_url = self.api_url + urllib2.urlparse.urljoin(self.url, path)
        return "%s.json?app_key=%s&%s" % (base_url, self._app_key, get)

    def get_url(self, method, username=None, password=None, post={},
                paginate={}):
        req = urllib2.Request(self._build_url(method, paginate))

        if Needish._auth_header:
            req.add_header(*Needish._auth_header)

        post = urllib.urlencode(post)
        try:
            handle = urllib2.urlopen(req, post)
            return handle.read()
        except urllib2.URLError, e:
            if hasattr(e, 'read'):
                return e.read()
            raise ConnectionError(str(e))
        
    def _parse_response(self, response):
        try:
            rsp = simplejson.loads(response)['rsp']
        except ValueError:
            # Should never happens but you never know
            raise InvalidFormat('Invalid JSON format: %s' % response)

        if 'status' in rsp:
            # if result is empty, response is not_found O_O 
            if rsp['status']['msg'] == 'not_found':
                rsp = {}
            else:
                raise InvalidFormat(rsp['status']['msg'])

        return rsp

    def authenticate(self, username, password):
        # Just keep authenticate information. We will use it in next posts.
        baseauth = '%s:%s' % (username, password)
        authheader =  "Basic %s" % baseauth.encode('base64').strip()
        Needish._auth_header = ("Authorization", authheader)


class Users(Needish):
    
    url = 'users/'

    def authenticate(self, username, password):
        """
        Authenticate a user with the given credentials. Returns the 
        authenticated user information.
        """
        super(Users, self).authenticate(username, password)
        return self.my()

    @authenticated
    def my(self):
        """Returns the authenticated user information."""
        res = self.get_url('authenticate')
        res = self._parse_response(res)
        return User(res['user']['User'])

    def friends(self, user_id, **paginate):
        """ Returns up to 100 friends of a given user, ordered by {order} """
        res = self.get_url('friends/%s/%s' % \
            (user_id, paginate.get('order', 'id')), paginate=paginate)
        res = self._parse_response(res)
        for user in res.get('friends', {}):
            yield User(user.get('User', {}))

    def profile(self, user_id, **paginate):
        """
        Returns the profile of a given user, including the recent needs. 
        The needs are ordered by descending date of creation.
        """
        res = self.get_url('profile/%s' % user_id, paginate=paginate)
        res = self._parse_response(res)
        user = res.get('user', {})
        return User(user.get('User', {}))


class Needs(Needish):
    
    url = 'needs/'

    @authenticated
    def add(self, subject, text, due_date=None):
        """ Post a new Need for the authenticating user. """
        post = dict(subject=subject, text=text)
        if due_date:
            post['due_date'] = due_date     

        res = self.get_url('add', post=post)
        res = self._parse_response(res)
        need = res.get('need', {})
        return Need(need.get('Need', {}), need.get('User', {}), self._app_key)

    def all(self, **paginate):
        """ 
        Returns up to 100 recent public needs, ordered by descending date 
        of creation. 
        """
        res = self.get_url('all', paginate=paginate)
        res = self._parse_response(res)
        for need in res.get('needs', {}):
            yield Need(need.get('Need', {}), need.get('User', {}), 
                       self._app_key)

    @authenticated
    def friends(self, **paginate):
        """
        Returns up to 100 recent needs of the authenticating user friends, 
        ordered by descending date of creation. 
        """
        res = self.get_url('friends', paginate=paginate)
        res = self._parse_response(res)
        for need in res.get('needs', {}):
            yield Need(need.get('Need', {}), need.get('User', {}),
                       self._app_key)

    @authenticated
    def follow(self, **paginate):
        """
        Returns up to 100 recent needs followed by the authenticating user,
        ordered by descending date of creation. 
        """
        res = self.get_url('follow', paginate=paginate)
        res = self._parse_response(res)
        for need in res.get('needs', {}):
            yield Need(need.get('Need', {}), need.get('User', {}),
                       self._app_key)
    
    def helps(self, need_id, **paginate):
        """
        Returns up to 100 helps for the given need, ordered by date 
        of creation.
        """
        res = self.get_url('helps/%s' % need_id, paginate=paginate)
        res = self._parse_response(res)
        for help in res.get('helps', {}):
            yield Help(help.get('Help', {}))
    
    @authenticated
    def helped(self, **paginate):
        """
        Returns up to 100 recent needs helped by the authenticating 
        user, ordered by descending date of creation of the need.
        """
        res = self.get_url('helped', paginate=paginate)
        res = self._parse_response(res)
        for need in res.get('needs', {}):
            yield Need(need.get('Need', {}), need.get('User', {}),
                       self._app_key)
    
    def hot(self, **paginate):
        """
        Returns the most active needs, ordered by descending number of 
        recent helps. 
        """
        res = self.get_url('hot', paginate=paginate)
        res = self._parse_response(res)
        for need in res.get('needs', {}):
            yield Need(need.get('Need', {}), need.get('User', {}),
                       self._app_key)
    
    def terms(self, terms, **paginate):
        """
        Returns up to 100 recent needs matching the terms sent, ordered 
        by descending date of creation.
        """
        res = self.get_url('search/%s' % terms, paginate=paginate)
        res = self._parse_response(res)
        for need in res.get('needs', {}):
            yield Need(need.get('Need', {}), need.get('User', {}),
                       self._app_key)

    def tag(self, tag, **paginate):
        """
        Returns up to 100 recent needs tagged with the given tag, 
        ordered by descending date of creation. 
        """
        res = self.get_url('tag/%s' % tag, paginate=paginate)
        res = self._parse_response(res)
        for need in res.get('needs', {}):
            yield Need(need.get('Need', {}), need.get('User', {}),
                       self._app_key)

    @authenticated
    def tracker(self, **paginate):
        """
        Returns up to 100 recent needs tracked by the authenticating user,
        ordered by descending date of creation. 
        """
        res = self.get_url('tracker', paginate=paginate)
        res = self._parse_response(res)
        for need in res.get('needs', {}):
            yield Need(need.get('Need', {}), need.get('User', {}),
                       self._app_key)

    def user(self, user_id, **paginate):
        """
        Returns up to 100 recent public needs created by the given user, 
        ordered by descending date of creation. 
        """
        res = self.get_url('user/%s' % user_id, paginate=paginate)
        res = self._parse_response(res)
        for need in res.get('needs', {}):
            yield Need(need.get('Need', {}), need.get('User', {}),
                       self._app_key)

    def view(self, need_id, **paginate):
        """
        Returns the given need, including up to 100 helps ordered by ascending
        date of creation. 
        """
        res = self.get_url('view/%s' % need_id, paginate=paginate)
        res = self._parse_response(res)
        need = res.get('need', {})
        return Need(need.get('Need', {}), need.get('User', {}), 
                    helps=need.get('Helps', {}), app_key=self._app_key)


class Helps(Needish):
    
    url = 'helps/'

    @authenticated
    def add(self, need_id, text):
        """Post a new Help, by the authenticating user, for the given need."""
        post = dict(text=text)

        res = self.get_url('add/%s' % need_id, post=post)
        res = self._parse_response(res)
        return Help(res.get('help', {}).get('Help', {}))
